import React from "react";
import Image from "./images";
import * as tf from "@tensorflow/tfjs";
import RubbishMap from "./assets/rubbish";
import { AutoCenter, Button, Card, ImageUploader, Dialog, Toast } from "antd-mobile";

/* 启动本地静态服务后，模型输出目录(model-save)访问地址 */
const MODEL_SAVE = "http://192.168.3.9:8080/model-save";

const RubbishList = [];
for (let key in RubbishMap) {
    RubbishList.push(RubbishMap[key]);
} console.log(RubbishList);

export default class App extends React.PureComponent {
    state = {
        imageFile: [],  // 上传的垃圾图片文件对象
        imageURL: "",   // 垃圾图片上传后的资源地址
        imageResult: "" // 模型对垃圾图片的预测结果
    }; // 组件内部的数据管理和状态控制

    async componentDidMount() {
        try {
            this.model = await tf.loadLayersModel(`${MODEL_SAVE}/model.json`);
            this.category = await fetch(`${MODEL_SAVE}/category.json`).then(res => res.json());
            this.model.summary(); console.log(this.category); // 模型图层结构 类别名称数组
        } catch (error) { console.log(error, error.message); }
    }

    handleImageUpload = (file) => {
        console.log(file); // 对象格式数据，无图片地址字段
        const imageURL = URL.createObjectURL(file); // 生成地址
        this.setState({ imageURL }, this.handleImagePredict);
        Toast.show({ icon: "loading", content: "正在处理中" });
        return { url: imageURL }; // 必须要如此返回，否则报错
    }; // 上传垃圾图片文件
    handleImagePredict = () => {
        const imageDOM = this.createImageElement();
        setTimeout(() => {
            const imagePredict = tf.tidy(() => {
                const imageTS = this.handleImage2Tensor(imageDOM);
                /* 使用模型对输入样本生成输出预测(获取输出) */
                return this.model.predict(imageTS); });
            imagePredict.print(); // 模型输出的张量数据
            this.handlePredictData(imagePredict);
        }, 500); // 若结果数据错误且始终不变，可加大延时时间
    }; // 使用模型对垃圾图片执行预测分类
    createImageElement = () => {
        const imageDOM = document.createElement("img");
        /* 在深度学习框架中, 图片分类模型的输入，图像尺寸必须是224*224
         * 创建的图片元素，是用来处理成模型输入的，故设定宽高均是224 */
        imageDOM.width = 224; imageDOM.height = 224;
        imageDOM.src = this.state.imageURL; return imageDOM;
    }; // 创建含有尺寸地址数据的图片元素
    handleImage2Tensor = (imageDOM) => {
        return tf.tidy(() => {
            /* tf.browser.fromPixels() 图片转换成张量 */
            return tf.browser.fromPixels(imageDOM)
                .toFloat().sub(255/2).div(255/2) // 数据归一化处理
                .reshape([1,224,224,3]); // 重塑图像张量的形状
        });
    }; // 处理图片元素以能够进入模型(图片元素转换成图像张量)
    handlePredictData = (imagePredict) => {
        /* 以嵌套数组的形式返回张量数据，将数据从张量转换成数组 */
        const predictResult = imagePredict.arraySync()[0]
            .map((value, index) => ({ value, name: this.category[index] }))
            .sort((a,b) => b.value - a.value); // 根据概率值大小降序排列
        const predictName = predictResult[0].name; // 概率值最大的类别名称
        const imageResult = Object.assign(predictResult[0], RubbishMap[predictName]);
        console.log(imageResult); this.setState({ imageResult }, Toast.clear);
    }; // 处理模型预测后输出数据(张量数据转换成普通数据)
    handlePredictShow = () => {
        const {
            name,  // 类别名称
            value, // 概率数值
            color, // 字体颜色
            handle // 投放要求
        } = this.state.imageResult;
        const percent = parseFloat((value*100).toFixed(2));
        return (<Card className="app-card" style={{color}}>
            <div className="app-card-name"><b>识别结果</b>{name}（{percent}%）</div>
            <div className="app-card-handle"><b>投放说明</b>{handle}</div>
        </Card>);
    }; // 垃圾图片预测结果清楚地显示在页面中
    handleImageDelete = () => {
        this.setState({ imageFile: [], imageURL: "", imageResult: "" });
    }; // 清除已上传的垃圾图片(清空所有状态)
    handleShowRubbish = (description) => {
        Dialog.show({
            content: description, closeOnAction: true,
            actions: [{ key: "know", text: "我知道喽" }],
            bodyStyle: { textIndent: "2em" } });
    }; // 显示垃圾类别的描述信息

    render() {
        const { imageFile, imageURL, imageResult } = this.state;
        const flexDirection = imageURL ? "column" : "row";
        return (<React.Fragment>
            <div className="app-header">
                <img alt="" src={Image.ImgBack}/>
                <div>
                    <AutoCenter className="app-title">欢迎使用垃圾图片智能分类小工具</AutoCenter>
                    <AutoCenter className="app-slogan">垃圾变宝源自分类，呵护环境始于点滴</AutoCenter>
                    <AutoCenter className="app-slogan">参与垃圾分类，保护地球家园，共创美好未来</AutoCenter>
                </div>
            </div>
            <ul className="app-list">{ RubbishList.map(rubbish => {
                const { name, image, backgroundColor, color, description } = rubbish;
                return (<li className="app-item" style={{backgroundColor,color}}
                            key={name} onClick={()=>{this.handleShowRubbish(description);}}>
                    <img alt="" src={image}/><p>{name}</p></li>);
            }) }</ul>
            <div className="app-main" style={{flexDirection}}>{ imageURL ?
                <React.Fragment>
                    <img alt="" className="app-image" src={imageURL}/>
                    <Button block className="app-delete" onClick={this.handleImageDelete}>删除当前图片，重新上传</Button>
                </React.Fragment> : <React.Fragment>
                    <ImageUploader className="app-uploader" value={imageFile} deletable={false}
                                   maxCount={1} preview={false} upload={this.handleImageUpload}/>
                    <p className="app-remind">点击上传图片，秒测垃圾类别<br/>拍摄时请使目标物居中并聚焦<br/>尽量使其他物品不要入镜</p>
                </React.Fragment>
            }</div>
            { imageResult && this.handlePredictShow() }
        </React.Fragment>);
    }
}
